﻿using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Device.Location;
using System.Windows;
using System.Windows.Controls;
namespace Phone7ActionPack.Controls
{
    [TemplateVisualState(GroupName = "WatcherStatus_States", Name = "ReadyState")]
    [TemplateVisualState(GroupName = "WatcherStatus_States", Name = "NoDataState")]
    [TemplateVisualState(GroupName = "WatcherStatus_States", Name = "InitializingState")]
    [TemplateVisualState(GroupName = "WatcherStatus_States", Name = "DisabledState")]
    [TemplateVisualState(GroupName = "WatcherPermission_States", Name = "DeniedState")]
    [TemplateVisualState(GroupName = "WatcherPermission_States", Name = "GrantedState")]
    [TemplateVisualState(GroupName = "WatcherPermission_States", Name = "UnknownState")]
    public class LocationVisualControl : Control, IDisposable
    {
        #region Location History

        private ObservableCollection<GeoPosition<GeoCoordinate>> history = new ObservableCollection<GeoPosition<GeoCoordinate>>();
        private ReadOnlyObservableCollection<GeoPosition<GeoCoordinate>> readonlyHistory;

        /// <summary>
        /// this is the collection that the public property History is based on that is accessable 
        /// for derived types 
        /// </summary>
        protected ObservableCollection<GeoPosition<GeoCoordinate>> LocationHistory
        {
            get { return this.history; }
        }

        /// <summary>
        /// collection of all the recorded points in the control has recorded
        /// </summary>
        public ReadOnlyObservableCollection<GeoPosition<GeoCoordinate>> History
        {
            get
            {
                if (readonlyHistory == null)
                    readonlyHistory = new ReadOnlyObservableCollection<GeoPosition<GeoCoordinate>>(history);
                return readonlyHistory;
            }
        }

        #endregion

        protected const string VISUAL_STATE_FORMAT = "{0}State";
        private GeoCoordinateWatcher watcher;
        #region GeoCoordinateWatcher Direct Interaction

        /// <summary>
        /// Method that directly enables the GeoCoordinateWatcher
        /// </summary>
        protected void StartWatcher()
        {

            // if permission is denyed then ask them for it. It could just be denied base on the devices location service being turned off as per MSDN http://msdn.microsoft.com/en-us/library/system.device.location.geocoordinatewatcher.permission(VS.92).aspx

            if (this.watcher.Permission == GeoPositionPermission.Granted) // if permission is granted don't ask them for permission
                this.watcher.Start(true);
            else
                this.watcher.Start(false);
            this.watcherStarted = true;
        }

        /// <summary>
        /// Method that directly disables the GeoCoordinateWatcher
        /// </summary>
        protected void StopWatcher()
        {
            this.watcher.Stop();
            this.watcherStarted = false;
        }


        #region Watcher Event Handlers
        private void watcher_StatusChanged(object sender, GeoPositionStatusChangedEventArgs e)
        {
            this.LocationStatus = e.Status;

        }

        private void watcher_PositionChanged(object sender, GeoPositionChangedEventArgs<GeoCoordinate> e)
        {

            this.Speed = e.Position.Location.Speed;
            this.Latitude = e.Position.Location.Latitude;
            this.Longitude = e.Position.Location.Longitude;
            this.VerticalAccuracy = e.Position.Location.VerticalAccuracy;
            this.HorizontalAccuracy = e.Position.Location.HorizontalAccuracy;
            this.IsUnknownLocation = e.Position.Location.IsUnknown;
            this.CurrentLocationTimestamp = e.Position.Timestamp;
            this.Course = e.Position.Location.Course;
            this.history.Add(e.Position);
        }

        void watcher_PermissionChanged(object sender, GeoPermissionChangedEventArgs e)
        {

        }

        #endregion

        #region Extensibility Watcher Event Callbacks
        /// <summary>
        /// This method will be called so that classes that extend the location Control 
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnWatcherPositionChanged(GeoPositionChangedEventArgs<GeoCoordinate> e) { }
        protected virtual void OnWatcherPermissionChanged(GeoPermissionChangedEventArgs e) { }
        protected virtual void OnWatcherPositionStatusChanged(GeoPositionStatusChangedEventArgs e) { }

        #endregion


        #endregion

        private bool watcherStarted;

        #region Events

        public event EventHandler LocationWatcherStarted;
        public event EventHandler LocationWatcherStopped;

        #endregion

        #region Dependency Properties

        #region Device Location
        #region LocationStatus

        public static readonly DependencyProperty LocationStatusProperty =
            DependencyProperty.Register("LocationStatus", typeof(GeoPositionStatus), typeof(LocationVisualControl), new PropertyMetadata(LocationStatusProperty_ChangedCallback));

        [Description("The device's Location Service Status")]
        [ReadOnly(true)]

        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Device Location")]
        public GeoPositionStatus LocationStatus
        {
            get { return (GeoPositionStatus)GetValue(LocationStatusProperty); }
            protected set { SetValue(LocationStatusProperty, value); }
        }

        /// <summary>
        /// DP changed call back for LocationStatus
        /// </summary>
        /// <param name="obj">instance of LocationControl that is having its LocationStatus property changed</param>
        /// <param name="e">the DP changed arguments</param>
        private static void LocationStatusProperty_ChangedCallback(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            LocationVisualControl element = obj as LocationVisualControl;
            // change to the visual state that matches the value supplied to the 
            VisualStateManager.GoToState(element, string.Format(VISUAL_STATE_FORMAT, e.NewValue), true);
            element.OnLocationStatusChanged(e);
        }

        /// <summary>
        /// Method will enable classes that derive from LocationControl to handle LocationStatus changes
        /// </summary>
        /// <param name="e">the DP changed arguments passed in to the DP changed callback</param>
        protected virtual void OnLocationStatusChanged(DependencyPropertyChangedEventArgs e)
        {

        }

        #endregion

        #region Speed

        public static readonly DependencyProperty SpeedProperty =
            DependencyProperty.Register("Speed", typeof(double), typeof(LocationVisualControl), new PropertyMetadata(SpeedProperty_ChangedCallback));

        [Description("The device's speed that it is reporting. I am guessing that it is in meters/seconds")]
        [ReadOnly(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Device Location")]
        public double Speed
        {
            get { return (double)GetValue(SpeedProperty); }
            protected set { SetValue(SpeedProperty, value); }
        }

        /// <summary>
        /// DP changed call back for Speed
        /// </summary>
        /// <param name="obj">instance of LocationControl that is having its Speed property changed</param>
        /// <param name="e">the DP changed arguments</param>
        private static void SpeedProperty_ChangedCallback(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            LocationVisualControl element = obj as LocationVisualControl;

            element.OnSpeedChanged(e);
        }

        /// <summary>
        /// Method will enable classes that derive from LocationControl to handle Speed changes
        /// </summary>
        /// <param name="e">the DP changed arguments passed in to the DP changed callback</param>
        protected virtual void OnSpeedChanged(DependencyPropertyChangedEventArgs e)
        {

        }

        #endregion

        #region Latitude

        public static readonly DependencyProperty LatitudeProperty =
            DependencyProperty.Register("Latitude", typeof(double), typeof(LocationVisualControl), new PropertyMetadata(LatitudeProperty_ChangedCallback));

        [Description("The device's Latitude that it is reporting.")]
        [ReadOnly(true)]
        [Category("Device Location")]
        public double Latitude
        {
            get { return (double)GetValue(LatitudeProperty); }
            protected set { SetValue(LatitudeProperty, value); }
        }

        /// <summary>
        /// DP changed call back for Latitude
        /// </summary>
        /// <param name="obj">instance of LocationControl that is having its Latitude property changed</param>
        /// <param name="e">the DP changed arguments</param>
        private static void LatitudeProperty_ChangedCallback(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            LocationVisualControl element = obj as LocationVisualControl;

            element.OnLatitudeChanged(e);
        }

        /// <summary>
        /// Method will enable classes that derive from LocationControl to handle Latitude changes
        /// </summary>
        /// <param name="e">the DP changed arguments passed in to the DP changed callback</param>
        protected virtual void OnLatitudeChanged(DependencyPropertyChangedEventArgs e)
        {

        }

        #endregion

        #region Longitude

        public static readonly DependencyProperty LongitudeProperty =
            DependencyProperty.Register("Longitude", typeof(double), typeof(LocationVisualControl), new PropertyMetadata(LongitudeProperty_ChangedCallback));

        [Description("The device's Longitude that it is reporting.")]
        [ReadOnly(true)]
        [Category("Device Location")]
        public double Longitude
        {
            get { return (double)GetValue(LongitudeProperty); }
            protected set { SetValue(LongitudeProperty, value); }
        }

        /// <summary>
        /// DP changed call back for Longitude
        /// </summary>
        /// <param name="obj">instance of LocationControl that is having its Longitude property changed</param>
        /// <param name="e">the DP changed arguments</param>
        private static void LongitudeProperty_ChangedCallback(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            LocationVisualControl element = obj as LocationVisualControl;

            element.OnLongitudeChanged(e);
        }

        /// <summary>
        /// Method will enable classes that derive from LocationControl to handle Longitude changes
        /// </summary>
        /// <param name="e">the DP changed arguments passed in to the DP changed callback</param>
        protected virtual void OnLongitudeChanged(DependencyPropertyChangedEventArgs e)
        {

        }

        #endregion

        #region HorizontalAccuracy

        public static readonly DependencyProperty HorizontalAccuracyProperty =
            DependencyProperty.Register("HorizontalAccuracy", typeof(double), typeof(LocationVisualControl), new PropertyMetadata(HorizontalAccuracyProperty_ChangedCallback));

        [Description("The device's margin of error for the Latitude that it is reporting in meters.")]
        [ReadOnly(true)]
        [Category("Device Location")]
        public double HorizontalAccuracy
        {
            get { return (double)GetValue(HorizontalAccuracyProperty); }
            protected set { SetValue(HorizontalAccuracyProperty, value); }
        }

        /// <summary>
        /// DP changed call back for HorizontalAccuracy
        /// </summary>
        /// <param name="obj">instance of LocationControl that is having its HorizontalAccuracy property changed</param>
        /// <param name="e">the DP changed arguments</param>
        private static void HorizontalAccuracyProperty_ChangedCallback(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            LocationVisualControl element = obj as LocationVisualControl;

            element.OnHorizontalAccuracyChanged(e);
        }

        /// <summary>
        /// Method will enable classes that derive from LocationControl to handle HorizontalAccuracy changes
        /// </summary>
        /// <param name="e">the DP changed arguments passed in to the DP changed callback</param>
        protected virtual void OnHorizontalAccuracyChanged(DependencyPropertyChangedEventArgs e)
        {

        }

        #endregion

        #region VerticalAccuracy

        public static readonly DependencyProperty VerticalAccuracyProperty =
            DependencyProperty.Register("VerticalAccuracy", typeof(double), typeof(LocationVisualControl), new PropertyMetadata(VerticalAccuracyProperty_ChangedCallback));

        [Description("The device's margin of error for the Latitude that it is reporting in meters.")]
        [ReadOnly(true)]
        [Category("Device Location")]
        public double VerticalAccuracy
        {
            get { return (double)GetValue(VerticalAccuracyProperty); }
            protected set { SetValue(VerticalAccuracyProperty, value); }
        }

        /// <summary>
        /// DP changed call back for VerticalAccuracy
        /// </summary>
        /// <param name="obj">instance of LocationControl that is having its VerticalAccuracy property changed</param>
        /// <param name="e">the DP changed arguments</param>
        private static void VerticalAccuracyProperty_ChangedCallback(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            LocationVisualControl element = obj as LocationVisualControl;

            element.OnVerticalAccuracyChanged(e);
        }

        /// <summary>
        /// Method will enable classes that derive from LocationControl to handle VerticalAccuracy changes
        /// </summary>
        /// <param name="e">the DP changed arguments passed in to the DP changed callback</param>
        protected virtual void OnVerticalAccuracyChanged(DependencyPropertyChangedEventArgs e)
        {

        }

        #endregion

        #region IsUnknownLocation

        public static readonly DependencyProperty IsUnknownLocationProperty =
            DependencyProperty.Register("IsUnknownLocation", typeof(bool), typeof(LocationVisualControl), new PropertyMetadata(IsUnknownLocationProperty_ChangedCallback));

        [Description("Does the device know where it is? True = no, False = yes. Common sense right?")]
        [ReadOnly(true)]
        [Category("Device Location")]
        public bool IsUnknownLocation
        {
            get { return (bool)GetValue(IsUnknownLocationProperty); }
            protected set { SetValue(IsUnknownLocationProperty, value); }
        }

        /// <summary>
        /// DP changed call back for IsUnknownLocation
        /// </summary>
        /// <param name="obj">instance of LocationControl that is having its IsUnknownLocation property changed</param>
        /// <param name="e">the DP changed arguments</param>
        private static void IsUnknownLocationProperty_ChangedCallback(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            LocationVisualControl element = obj as LocationVisualControl;

            element.OnIsUnknownLocationChanged(e);
        }

        /// <summary>
        /// Method will enable classes that derive from LocationControl to handle IsUnknownLocation changes
        /// </summary>
        /// <param name="e">the DP changed arguments passed in to the DP changed callback</param>
        protected virtual void OnIsUnknownLocationChanged(DependencyPropertyChangedEventArgs e)
        {

        }

        #endregion

        #region GeoCoordinate

        public static readonly DependencyProperty GeoCoordinateProperty =
            DependencyProperty.Register("GeoCoordinate", typeof(GeoCoordinate), typeof(LocationVisualControl), new PropertyMetadata(GeoCoordinateProperty_ChangedCallback));

        [Description("GeoCoordinate is the Windows Phone 7's default class to identify a location")]
        [ReadOnly(true)]
        [Category("Device Location")]
        public GeoCoordinate GeoCoordinate
        {
            get { return (GeoCoordinate)GetValue(GeoCoordinateProperty); }
            protected set { SetValue(GeoCoordinateProperty, value); }
        }

        /// <summary>
        /// DP changed call back for GeoCoordinate
        /// </summary>
        /// <param name="obj">instance of LocationControl that is having its GeoCoordinate property changed</param>
        /// <param name="e">the DP changed arguments</param>
        private static void GeoCoordinateProperty_ChangedCallback(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            LocationVisualControl element = obj as LocationVisualControl;

            element.OnGeoCoordinateChanged(e);
        }

        /// <summary>
        /// Method will enable classes that derive from LocationControl to handle GeoCoordinate changes
        /// </summary>
        /// <param name="e">the DP changed arguments passed in to the DP changed callback</param>
        protected virtual void OnGeoCoordinateChanged(DependencyPropertyChangedEventArgs e)
        {

        }

        #endregion

        #region CurrentLocationTimestamp

        public static readonly DependencyProperty CurrentLocationTimestampProperty =
            DependencyProperty.Register("CurrentLocationTimestamp", typeof(DateTimeOffset), typeof(LocationVisualControl), new PropertyMetadata(CurrentLocationTimestampProperty_ChangedCallback));

        [Description("This is the timestamp of the last reading that the device took")]
        [ReadOnly(true)]
        [Category("Device Location")]
        public DateTimeOffset CurrentLocationTimestamp
        {
            get { return (DateTimeOffset)GetValue(CurrentLocationTimestampProperty); }
            protected set { SetValue(CurrentLocationTimestampProperty, value); }
        }

        /// <summary>
        /// DP changed call back for CurrentLocationTimestamp
        /// </summary>
        /// <param name="obj">instance of LocationControl that is having its CurrentLocationTimestamp property changed</param>
        /// <param name="e">the DP changed arguments</param>
        private static void CurrentLocationTimestampProperty_ChangedCallback(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            LocationVisualControl element = obj as LocationVisualControl;

            element.OnCurrentLocationTimestampChanged(e);
        }

        /// <summary>
        /// Method will enable classes that derive from LocationControl to handle CurrentLocationTimestamp changes
        /// </summary>
        /// <param name="e">the DP changed arguments passed in to the DP changed callback</param>
        protected virtual void OnCurrentLocationTimestampChanged(DependencyPropertyChangedEventArgs e)
        {

        }

        #endregion

        #region Course

        public static readonly DependencyProperty CourseProperty =
            DependencyProperty.Register("Course", typeof(double), typeof(LocationVisualControl), new PropertyMetadata(CourseProperty_ChangedCallback));
        [Description("The Course that the device is heading in 0=North, 90=East, 180=South, 270=West")]
        [ReadOnly(true)]
        [Category("Device Location")]
        public double Course
        {
            get { return (double)GetValue(CourseProperty); }
            protected set { SetValue(CourseProperty, value); }
        }

        /// <summary>
        /// DP changed call back for Course
        /// </summary>
        /// <param name="obj">instance of LocationControl that is having its Course property changed</param>
        /// <param name="e">the DP changed arguments</param>
        private static void CourseProperty_ChangedCallback(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            LocationVisualControl element = obj as LocationVisualControl;

            element.OnCourseChanged(e);
        }

        /// <summary>
        /// Method will enable classes that derive from LocationControl to handle Course changes
        /// </summary>
        /// <param name="e">the DP changed arguments passed in to the DP changed callback</param>
        protected virtual void OnCourseChanged(DependencyPropertyChangedEventArgs e)
        {

        }

        #endregion
        #endregion

        #region Device Location Control

        #region GeoPositionPermission

        public static readonly DependencyProperty GeoPositionPermissionProperty =
            DependencyProperty.Register("GeoPositionPermission", typeof(GeoPositionPermission), typeof(LocationVisualControl), new PropertyMetadata(GeoPositionPermissionProperty_ChangedCallback));

        public GeoPositionPermission GeoPositionPermission
        {
            get { return (GeoPositionPermission)GetValue(GeoPositionPermissionProperty); }
            protected set { SetValue(GeoPositionPermissionProperty, value); }
        }

        /// <summary>
        /// DP changed call back for GeoPositionPermission
        /// </summary>
        /// <param name="obj">instance of LocationControl that is having its GeoPositionPermission property changed</param>
        /// <param name="e">the DP changed arguments</param>
        private static void GeoPositionPermissionProperty_ChangedCallback(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            LocationVisualControl element = obj as LocationVisualControl;
            // change the visual state of the control to the state that reflects permissions
            VisualStateManager.GoToState(element, string.Format(VISUAL_STATE_FORMAT, e.NewValue), true);
            element.OnGeoPositionPermissionChanged(e);
        }

        /// <summary>
        /// Method will enable classes that derive from LocationControl to handle GeoPositionPermission changes
        /// </summary>
        /// <param name="e">the DP changed arguments passed in to the DP changed callback</param>
        protected virtual void OnGeoPositionPermissionChanged(DependencyPropertyChangedEventArgs e)
        {

        }

        #endregion

        #region LocationWatcherEnabled

        public static readonly DependencyProperty LocationWatcherEnabledProperty =
            DependencyProperty.Register("LocationWatcherEnabled", typeof(bool), typeof(LocationVisualControl), new PropertyMetadata(LocationWatcherEnabledProperty_ChangedCallback));

        [Description("Is the Location Watcher Enabled")]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Device Location Control")]
        public bool LocationWatcherEnabled
        {
            get { return (bool)GetValue(LocationWatcherEnabledProperty); }
            set { SetValue(LocationWatcherEnabledProperty, value); }
        }

        /// <summary>
        /// DP changed call back for LocationWatcherEnabled
        /// </summary>
        /// <param name="obj">instance of LocationControl that is having its LocationWatcherEnabled property changed</param>
        /// <param name="e">the DP changed arguments</param>
        private static void LocationWatcherEnabledProperty_ChangedCallback(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            LocationVisualControl element = obj as LocationVisualControl;
            if (element.watcherStarted)
                element.StopWatcher();
            else
                element.StartWatcher();
            element.OnLocationWatcherEnabledChanged(e);
        }

        /// <summary>
        /// Method will enable classes that derive from LocationControl to handle LocationWatcherEnabled changes
        /// </summary>
        /// <param name="e">the DP changed arguments passed in to the DP changed callback</param>
        protected virtual void OnLocationWatcherEnabledChanged(DependencyPropertyChangedEventArgs e)
        {

        }

        #endregion

        #endregion

        #endregion

        public LocationVisualControl()
        {
            watcher = new System.Device.Location.GeoCoordinateWatcher();
            this.DefaultStyleKey = typeof(LocationVisualControl);
            if (System.ComponentModel.DesignerProperties.IsInDesignTool)
                this.InitDesignTime();
            else
                this.InitRunTime();
        }

        #region Contorl Init
        protected virtual void InitDesignTime()
        {

        }
        protected virtual void InitRunTime()
        {


            watcher.PermissionChanged += new EventHandler<System.Device.Location.GeoPermissionChangedEventArgs>(watcher_PermissionChanged);
            watcher.PositionChanged += new EventHandler<System.Device.Location.GeoPositionChangedEventArgs<System.Device.Location.GeoCoordinate>>(watcher_PositionChanged);
            watcher.StatusChanged += new EventHandler<System.Device.Location.GeoPositionStatusChangedEventArgs>(watcher_StatusChanged);

        }

        #endregion

        public void Dispose()
        {
            this.watcher.Stop();
            this.watcher.Dispose();
        }
    }
}